# -*- coding: utf-8 -*-
import os
import datetime
import time
import shutil
import configparser
import logging
import sys

logging.basicConfig(level=logging.INFO)

cnf_file = "myback.ini"
config = configparser.ConfigParser()
config.read(cnf_file)


master_container_name = config.get("master","master_container_name")
out_work_dir = config.get("myback","out_work_dir")
parsing_binlog = config.get("myback","parsing_binlog")
lastFile = os.path.join(out_work_dir,"lastFile.log")
master_container_inner_data_path = config.get("master","master_container_inner_data_path")
binlog_sql_dir = config.get("myback","binlog_sql_dir")
slave_container_name = config.get("slave", "slave_container_name")
slave_user = config.get("slave", "slave_user")
slave_password = config.get("slave", "slave_password")

def usage():
    print("用法：python3 {0} [datetime]".format(os.path.basename(__file__)))
    print("datetime: 需要还原到哪个时间节点,不指定则还原到当前时间点")
    print("eg: pyhton3 {0} '2021-08-01 10:12:33'".format(os.path.basename(__file__)))
    print("eg: pyhton3 {0}".format(os.path.basename(__file__)))

def get_stop_at():
    if len(sys.argv) < 2 or not sys.argv[1]:
        stop_at = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    else:
        try:
            datetime.datetime.strptime(sys.argv[1], '%Y-%m-%d %H:%M:%S')
            stop_at = sys.argv[1]
        except:
            print("错误：时间日期格式不正确")
            usage()
            exit(1)
    return stop_at


def exec(stop_at):
    logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------解压备份文件")
    os.system("docker exec -i myback python3 xbstream.py '{}'".format(stop_at))
    logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------解压备份文件完成")

    print('\n' * 1)

    logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------准备备份文件")
    os.system("docker exec -i myback python3 apply_log_for_backup.py '{}'".format(stop_at))
    logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------准备备份文件完成")

    print('\n' * 1)

    logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------停止新库")
    os.system("docker stop {}".format(slave_container_name))
    logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------停止新库完成")

    print('\n' * 1)
    logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------等待2秒...")
    time.sleep(2)

    logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------新库原数据目录备份")
    slave_container_out_data_path = config.get("slave", "slave_container_out_data_path")
    #os.system("cp -r {0} {1}".format(slave_container_out_data_path,slave_container_out_data_path + "_" + datetime.datetime.now().strftime('%Y%m%d%H%M%S')))
    for it in os.listdir(slave_container_out_data_path):
        if os.path.isdir(os.path.join(slave_container_out_data_path, it)):
            shutil.rmtree(os.path.join(slave_container_out_data_path, it))
        else:
            os.remove(os.path.join(slave_container_out_data_path, it))
    logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------新库原数据目录备份完成")


    print('\n' * 1)
    logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------等待2秒...")
    time.sleep(2)

    logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------将备份数据还原到新库...")
    os.system("docker exec -i myback python3 restore_backup_data.py '{}'".format(stop_at))
    logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------将备份数据还原到新库完成...")


    print('\n' * 1)
    logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------等待3秒后将启动新库...")
    time.sleep(3)

    # 第五步启动新库
    logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------启动新库...")
    os.system("docker start {}".format(slave_container_name))
    logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------启动新库完成...")

    print('\n' * 1)
    if parsing_binlog == "1":
        logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------等待五秒后执行binlog...")
        time.sleep(5)

        logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------抽取binlog开始")
        restore_database_binlog(get_last_file())
        logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------抽取binlog结束")

        print('\n' * 1)
        logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------等待2秒后binlog导入新库...")
        time.sleep(2)


        logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------binlog导入新库开始")
        run_database_binlog()
        logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------binlog导入新库结束")
    else:
        logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------配置指定不需要解析binlog")


    print('\n' * 1)
    logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------清除工作目录开始")
    clean_binlog_sql_file()
    clean_work_file()
    logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------清除工作目录结束")

    print('\n' * 1)
    logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------全部恢复工作完成！！！")
    print('\n' * 1)

def restore_database_binlog(last_backup_file):
    master_container_out_data_path =config.get("master","master_container_out_data_path")
    last_backup_file =os.path.join(out_work_dir,last_backup_file,"xtrabackup_info")
    backup_position_binlog_file = None
    backup_position = None
    with open(last_backup_file, 'r') as f:
        lines = f.readlines()
        for line in lines:
            if "binlog_pos = filename " in line:
                backup_position_binlog_file = line.replace("binlog_pos = filename ", "").split(",")[0]
                backup_position_binlog_file = backup_position_binlog_file.replace("'", "")
                backup_position = line.replace("binlog_pos = filename ", "").split(",")[1].strip()
                backup_position = backup_position.split(" ")[1].replace("'", "")
                pass
            else:
                continue
        binlog_path = os.path.dirname(master_container_out_data_path)
        binlog_files = os.listdir(master_container_out_data_path)
        #如果没有找到binlog，忽略binlog的还原
        if not binlog_files:
            print("未找到有效binlog")
            exit(1)

        #对binlog文件排序，按顺序遍历binlog，获取binlog的最后的修改时间，与stop_at做对比，判断还原的过程是否需要某个binlogfile
        binlog_files.sort()

        binlog_files_for_restore = []
        # 恢复数据库的指定时间点
        stop_at_time =  datetime.datetime.strptime(stop_at, '%Y-%m-%d %H:%M:%S')
        for binlog in binlog_files:
            if (".index" in binlog or "relay" in binlog):
                continue
            if ("mysql-bin." in binlog):
                #保留最后一个备份中的binlog，以及其后面的binlog，这部分binlog会在还原的时候用到
                if (int(binlog.split(".")[-1]) >= int(backup_position_binlog_file.split(".")[-1])):
                    binlog_files_for_restore.append(binlog)

        binlog_file_count = 0
        #第一个文件，从上最后一个差异备份的position位置开始，最后一个文件，需要stop_at到指定的时间
        for binlog in binlog_files_for_restore:
            if not os.path.isdir(binlog):
                #binlog物理文件的最后修改时间
                binlog_file_updatetime = datetime.datetime.strptime(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(os.stat(binlog_path+"/"+binlog).st_mtime)),'%Y-%m-%d %H:%M:%S')
                print("{0}---{1}".format(binlog,binlog_file_updatetime))
                print("结束时间{0}".format(stop_at_time))
                print(stop_at_time > binlog_file_updatetime)
                #判断binlog的生成时间，是否大于stop_at，对于修改时间大于stop_at的日志，需要全部还原，不需要stop_at指定截止点
                if  stop_at_time > binlog_file_updatetime :
                    if (binlog_file_count < 1):
                        if (len(binlog_files_for_restore) == 1):
                            # 找到差异备份之后的第一个binlog，需要根据差异备份的position，来过来第一个binlog文件
                            restore_commond = '''docker exec -i {0} mysqlbinlog {1}  --skip-gtids=true --start-position={2}  --stop-datetime="{3}" > {4}/{5}.sql''' \
                                .format(master_container_name,master_container_inner_data_path + binlog, backup_position, stop_at, binlog_sql_dir,binlog)
                            print("execute the following command....")
                            print(restore_commond)
                            os.system(restore_commond)
                            print("complete!!!")
                            binlog_file_count = binlog_file_count + 1
                        else:
                            # 找到差异备份之后的第一个binlog，需要根据差异备份的position，来过来第一个binlog文件
                            restore_commond = '''docker exec -i {0} mysqlbinlog {1}  --skip-gtids=true --start-position={2}  > {3}/{4}.sql''' \
                                .format(master_container_name,master_container_inner_data_path + binlog, backup_position,binlog_sql_dir,binlog)
                            print("execute the following command....")
                            print(restore_commond)
                            os.system(restore_commond)
                            print("complete!!!")
                            binlog_file_count = binlog_file_count + 1
                    else:
                        # 从第二个文件开始，binlog需要全部还原
                        restore_commond = '''docker exec -i {0} mysqlbinlog {1}  --skip-gtids=true > {2}/{3}.sql ''' \
                            .format(master_container_name,master_container_inner_data_path + binlog, binlog_sql_dir,binlog)
                        print("execute the following command....")
                        print(restore_commond)
                        os.system(restore_commond)
                        print("complete!!!")
                        binlog_file_count = binlog_file_count + 1
                else:
                      if (binlog_file_count < 1):
                          restore_commond = '''docker exec -i {0} mysqlbinlog {1}  --skip-gtids=true --start-position={2} --stop-datetime="{3}" > {4}/{5}.sql'''.format(master_container_name,master_container_inner_data_path + binlog, backup_position,stop_at, binlog_sql_dir,binlog)
                          print("execute the following command....")
                          print(restore_commond)
                          os.system(restore_commond)
                          print("complete!!!")
                          binlog_file_count = binlog_file_count + 1
                      else:
                          if (binlog_file_count >= 1):
                              restore_commond = '''docker exec -i {0} mysqlbinlog {1}  --skip-gtids=true --stop-datetime="{2}" > {3}/{4}.sql'''.format(master_container_name,master_container_inner_data_path + binlog, stop_at, binlog_sql_dir,binlog)
                              print("execute the following command....")
                              print(restore_commond)
                              os.system(restore_commond)
                              print("complete!!!")
                              binlog_file_count = binlog_file_count + 1
                              break

def get_last_file():
    last_file = None
    if os.path.exists(lastFile):
        with open(lastFile, 'r') as f:
            lines = f.readlines()
            last_line = lines[-1]  # get last backup name
            if (last_line):
                last_file = last_line
    return last_file.replace("\n","")

def run_database_binlog():
    binlog_sql_files = os.listdir(binlog_sql_dir)
    if not binlog_sql_files:
        print("无可用于恢复的binlog sql")
        exit(1)

    print(binlog_sql_files)
    binlog_sql_files.sort()

    for binlog_sql in binlog_sql_files:
        pass
        restore_commond = '''docker exec -i {0} mysql -u{1} -p{2} -f --max_allowed_packet=1073741824 < {3}/{4}''' \
            .format(slave_container_name, slave_user, slave_password, binlog_sql_dir, binlog_sql)
        print("execute the following command....")
        print(restore_commond)
        os.system(restore_commond)

def clean_binlog_sql_file():
    for binlog_sql in os.listdir(binlog_sql_dir):
        if os.path.isdir(os.path.join(binlog_sql_dir, binlog_sql)):
            shutil.rmtree(os.path.join(binlog_sql_dir, binlog_sql))
        else:
            os.remove(os.path.join(binlog_sql_dir, binlog_sql))

def clean_work_file():
    for work_it in os.listdir(out_work_dir):
        if os.path.isdir(os.path.join(out_work_dir, work_it)):
            shutil.rmtree(os.path.join(out_work_dir, work_it))
        else:
            os.remove(os.path.join(out_work_dir, work_it))

def detect_users():
    if os.getuid() != 0:
        print("错误：此脚本需要切换到root用户执行。<sudo su>")
        exit(1)

if __name__ == '__main__':
    detect_users()
    stop_at = get_stop_at()
    exec(stop_at)
